package com.ruiyian.netty.nettyclient;

import com.google.common.collect.Lists;
import com.ruiyian.common.constant.*;
import com.ruiyian.common.core.domain.AjaxResult;
import com.ruiyian.common.core.domain.entity.Point;
import com.ruiyian.common.core.redis.RedisCache;
import com.ruiyian.common.enums.ReturnType;
import com.ruiyian.common.thread.ThreadPoolUtil;
import com.ruiyian.common.utils.JSON;
import com.ruiyian.common.utils.jfinsUtil.AnalysisInfomationBoard;
import com.ruiyian.common.utils.jfinsUtil.FinsMessage;
import com.ruiyian.common.utils.jfinsUtil.FinsMessageUtils;
import com.ruiyian.common.utils.jfinsUtil.Hex;
import com.ruiyian.common.utils.spring.SpringUtils;
import com.ruiyian.common.vo.ReturnPoint;
import com.ruiyian.netty.nettyserver.MyChannelHandlerPool;
import com.ruiyian.project.equipment.service.EquipmentStatusService;
import com.ruiyian.project.log.domain.OperationLog;
import com.ruiyian.project.log.service.OperationLogService;
import com.ruiyian.system.domain.ServerEq;
import com.ruiyian.system.domain.vo.ServerEqFormal;
import com.ruiyian.system.service.ServerService;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author chong
 * @email 673426164@qq.com
 * @create 2021/2/4
 * Desc: netty客户端业务操作
 */
@Slf4j
@ChannelHandler.Sharable
public class ClientHandler extends ChannelInboundHandlerAdapter {
    private static final String HEART_BEAT = "heart beat!";
    private int nowIndex = 0; // 指针索引
    // 操作日志
    private List<OperationLog> operationLogList = Lists.newArrayList();
    private OperationLog operationLog = null;
    // 注入日志操作类
    private static OperationLogService operationLogService;
    private static EquipmentStatusService equipmentStatusService;
    private static RedisCache redisCache;
    private static JdbcTemplate jdbcTemplate;
    private List<Point> data = null;
    private List<Point> returnPointList = new ArrayList<>();
    // 在这个list里面的ip采用通过fins报文进行握手
    private List<String> finsiplist = new ArrayList<>();
    // modbus设备
    private List<String> modusiplist = new ArrayList<>();
    // 串口服务器
    private List<String> rxtxiplist = new ArrayList<>();

    static {
        // 命令头
        operationLogService = SpringUtils.getBean(OperationLogService.class);
        redisCache = SpringUtils.getBean(RedisCache.class);
        equipmentStatusService = SpringUtils.getBean(EquipmentStatusService.class);
        jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class);
    }

    /**
     * 初始化操作 获取对应参数
     */
    void setValue() {
        // 操作点位集合
        data = redisCache.getCacheList(Constants.POINTLIST);
    }

    /**
     * 客户端接收plc消息
     * 业务逻辑
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            // 获取当前激活模块（消防箱的已经定时代码中搞好了）
            String currentactivemodule = redisCache.getCacheObject("CurrentActiveModule");
            // 返回消息
            JSONObject jsonReturn = new JSONObject();
            InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
            int port = ipSocket.getPort();
            String host = ipSocket.getHostString();
            String serverpath = host + ":" + port;
            // modbus设备数据处理，就代表智能消防箱
            if(modusiplist.contains(serverpath)){
                log.info(ipSocket.getHostString() + port + "modbus接收到消息:{}", msg);
                // 取出当前请求的firebox id
                String  CurrentRequestFireBoxId = redisCache.getCacheObject("CurrentRequestFireBox");
                String msgstr = msg.toString();
                // 说明数据没问题
                if(msgstr.indexOf("0103")!=-1){
                    String fire_box_data = msgstr.substring(msgstr.indexOf("0103")+6);
                    System.out.println(fire_box_data);
                    // 获取终端数量 一个字有4位字符串，每个终端有10个字
                    for(int i=0;i<fire_box_data.length()/40;i++){
                        // 生成每个终端的数据名称
                        String one_firebox_data_key = CurrentRequestFireBoxId+"/"+i;
                        // 截取该终端的数据
                        String one_firebox_data = fire_box_data.substring(i*40, (i+1)*40);
                        // 将该终端的数据存入redis
                        redisCache.setCacheObject(one_firebox_data_key, one_firebox_data);
                    }
                }
            }
            // 情报板等串口设备
            else if(rxtxiplist.contains(serverpath)){
                log.info(ipSocket.getHostString() + "rxtx接收到消息:{}", msg);
            }
            // fins设备的数据处理
            else if(finsiplist.contains(serverpath)){
                // 判断是否有fins数据报文头
                if (!msg.toString().contains(Hex.encodeHexStr(FinsMessage.FINS_HEADER_BYTE, false))) {
                    return;
                }
                log.info(ipSocket.getHostString() + "Plc接收到消息:{}", msg);
                // 分析返回数据是什么类型的数据
                String messageType = FinsMessageUtils.analysisMessage(msg);
                System.out.println("返回消息类型："+messageType);
                if (StringUtils.isBlank(messageType)) {
                    return;
                }
                if (messageType.equals("handshakeMessage")) {
                    log.info(serverpath + ":握手成功");
                    return;
                }
                Map<String, ChannelFuture> connectMap = NettyStarter.getConnectMap();
                FinsMessage finsMessage = null;
                String recoderserverpath = redisCache.getCacheObject("recoderserverpath"); // 上一次发送的plcip地址
                // 判断下是不是二次管控的设备
                // 取出当前数据上发的是哪一个隧道的什么设备
                String sql = "SELECT * FROM tb_server_2 WHERE `host`=? AND `port`=?";
                String[] sqlStr = new String[2];
                sqlStr[0] = host;
                sqlStr[1] = Integer.toString(port);
                List<Map<String, Object>> result =  jdbcTemplate.queryForList(sql, sqlStr);
                if(result.size()!=1){
                    return;
                }
                String msgTunnelId = String.valueOf(result.get(0).get("tunnel_id"));
                String MsgModuleId = String.valueOf(result.get(0).get("module_id"));
                if (messageType.equals("readMessage")) {
                    // 对模块进行判断
                    // 如果是二次事故的模块
                    if(MsgModuleId.equals(Integer.toString(ModuleIdList.SECONDARY_ACCIDENT))){
                        // 获取当前激活设备
                        String  CurrentRequestFinsDevId = redisCache.getCacheObject("CurrentRequestSecondryFinsDevId_"+msgTunnelId);
                        // 如果存在激活点位，那么就获取该点位的数据，并存入redis
                        if(CurrentRequestFinsDevId!=null && !CurrentRequestFinsDevId.equals("0")){
                            String queryresultstr = msg.toString();
                            // 存入redis
                            redisCache.setCacheObject(CurrentRequestFinsDevId, queryresultstr.substring(queryresultstr.length()-1));
                            redisCache.setCacheObject("CurrentRequestSecondryFinsDevId_"+msgTunnelId, "0");
                        }
                    }
                    // 如果是视觉诱导模块的数据（数据需要稍作处理）
                    else if(MsgModuleId.equals(Integer.toString(ModuleIdList.VISUAL_INDUCTION))){
                        // 获取当前激活设备
                        String  CurrentRequestFinsDevId = redisCache.getCacheObject("CurrentRequestVisualFinsDevId_"+msgTunnelId);
                        // 如果存在激活点位，那么就获取该点位的数据，并存入redis
                        if(CurrentRequestFinsDevId!=null && !CurrentRequestFinsDevId.equals("0")){
                            String queryresultstr = msg.toString();
                            String visualData = queryresultstr.substring(queryresultstr.length()-2);
                            int visualIntData = Integer.parseInt(visualData, 16);
                            // 存入redis
                            redisCache.setCacheObject(CurrentRequestFinsDevId, Integer.toString(visualIntData));
                            redisCache.setCacheObject("CurrentRequestVisualFinsDevId_"+msgTunnelId, "0");
                        }
                    }
                    // 如果是照明控制模块
                    else if(MsgModuleId.equals(Integer.toString(ModuleIdList.LIGHT_CONTROL))){
                        // 获取当前激活设备
                        String  CurrentRequestFinsDevId = redisCache.getCacheObject("CurrentRequestLightControlFinsDevId_"+msgTunnelId);
                        // 如果存在激活点位，那么就获取该点位的数据，并存入redis
                        if(CurrentRequestFinsDevId!=null && !CurrentRequestFinsDevId.equals("0")){
                            String queryresultstr = msg.toString();
                            // 存入redis
                            redisCache.setCacheObject(CurrentRequestFinsDevId, queryresultstr.substring(queryresultstr.length()-1));
                            redisCache.setCacheObject("CurrentRequestLightControlFinsDevId_"+msgTunnelId, "0");
                        }
                    }
                    // 如果是交通控制模块
                    else if(MsgModuleId.equals(Integer.toString(ModuleIdList.TRAFFIC_CONTROL))){
                        // 获取当前激活设备
                        String  CurrentRequestFinsDevId = redisCache.getCacheObject("CurrentRequestTrafficControlFinsDevId_"+msgTunnelId);
                        // 如果存在激活点位，那么就获取该点位的数据，并存入redis
                        if(CurrentRequestFinsDevId!=null && !CurrentRequestFinsDevId.equals("0")){
                            String queryresultstr = msg.toString();
                            // 存入redis
                            redisCache.setCacheObject(CurrentRequestFinsDevId, queryresultstr.substring(queryresultstr.length()-1));
                            redisCache.setCacheObject("CurrentRequestTrafficControlFinsDevId_"+msgTunnelId, "0");
                        }
                    }
                    // 如果是通风控制模块
                    else if(MsgModuleId.equals(Integer.toString(ModuleIdList.FAN_CONTROL))){
                        // 获取当前激活设备
                        String  CurrentRequestFinsDevId = redisCache.getCacheObject("CurrentRequestFanControlFinsDevId_"+msgTunnelId);
                        // 如果存在激活点位，那么就获取该点位的数据，并存入redis
                        if(CurrentRequestFinsDevId!=null && !CurrentRequestFinsDevId.equals("0")){
                            String queryresultstr = msg.toString();
                            // 存入redis
                            redisCache.setCacheObject(CurrentRequestFinsDevId, queryresultstr.substring(queryresultstr.length()-1));
                            redisCache.setCacheObject("CurrentRequestFanControlFinsDevId_"+msgTunnelId, "0");
                        }
                    }
                    return;
                }
                // fins-plc设备有时写入操作，有时会有反馈，有时不会有，所以这里的判断是比较鸡肋的
                if (messageType.equals("writeMessage")) {
                    // 从缓存中获取上次操作信息
                    String type =  "writereturn";
                    Long tunnelId = redisCache.getCacheObject("tunnelId");
                    Long moduleId = redisCache.getCacheObject("moduleId");
                    Long memoryArea = redisCache.getCacheObject("memoryArea");
                    Long registerAddress = redisCache.getCacheObject("registerAddress");
                    // 创建返回数组
                    Map<String, Object> writeresultmap = new HashMap<>();
                    writeresultmap.put("type", type);
                    writeresultmap.put("tunnelId", tunnelId);
                    writeresultmap.put("moduleId", moduleId);
                    writeresultmap.put("memoryArea", memoryArea);
                    writeresultmap.put("registerAddress", registerAddress);
                    writeresultmap.put(AjaxResult.CODE_TAG, HttpStatus.SUCCESS);
                    // 判断是否是正在连接的设备,发回数据给往网页
                    if(recoderserverpath.equals(host+":"+port)){
//                        MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(JSON.stringify(writeresultmap)));
                    }
                    return;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    // 创建日志list
    void createOperaLog(String equipType, String equipName, String command, String sendStatus, String type, String controlGroup, Integer executeSeq, String operation, String pileNum, String value, Long eqId, Long tunnelId) {
        operationLog = new OperationLog(equipType, equipName, null, command, sendStatus, type, controlGroup, executeSeq, operation, pileNum, value, eqId, tunnelId);
        operationLog.setSendTime(new Date());
        operationLogList.add(operationLog);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
    }
    /**
     * 关闭连接
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.error("客户端与设备" + host + ":" + port + "连接断开!");
        NettyStarter.setConnectRemove(host, port);
        ctx.channel().closeFuture().sync();
        ctx.close();
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                NettyStarter.doConnect(host, port);
            }
        }, 5L, TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

    /**
     * 空闲心跳机制
     */
  /*  @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {// 写空闲时发心跳
                System.out.println("发送心跳");
                //log.info("发送心跳");
                ctx.writeAndFlush(HEART_BEAT + "\n");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }*/

    /**
     * 建立连接
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        String serverpath = host + ":" + port;
        // 得到获取服务列表的bean
        ServerService serverService = SpringUtils.getBean(ServerService.class);
        List<ServerEqFormal> allserverlist = serverService.selectServerformalByInfo(0, 0, null);
        // 将几种设备的ip地址存储起来
        for(ServerEqFormal onesrver:allserverlist){
            if(onesrver.getServer_type().equals("finsplc")){
                // 二次事故
                if(onesrver.getModule_id()==ModuleIdList.SECONDARY_ACCIDENT){
                    redisCache.setCacheObject("CurrentRequestSecondryFinsDevId_"+onesrver.getTunnel_id(), "0");
                }
                // 视觉诱导
                else if(onesrver.getModule_id()==ModuleIdList.VISUAL_INDUCTION){
                    redisCache.setCacheObject("CurrentRequestVisualFinsDevId_"+onesrver.getTunnel_id(), "0");
                }
                // 照明控制
                else if(onesrver.getModule_id()==ModuleIdList.LIGHT_CONTROL){
                    redisCache.setCacheObject("CurrentRequestLightControlFinsDevId_"+onesrver.getTunnel_id(), "0");
                }
                // 交通控制
                else if(onesrver.getModule_id()==ModuleIdList.TRAFFIC_CONTROL){
                    redisCache.setCacheObject("CurrentRequestTrafficControlFinsDevId_"+onesrver.getTunnel_id(), "0");
                }
                // 通风控制
                else if(onesrver.getModule_id()==ModuleIdList.FAN_CONTROL){
                    redisCache.setCacheObject("CurrentRequestFanControlFinsDevId_"+onesrver.getTunnel_id(), "0");
                }
                finsiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
            // modbus就代表智能消防箱
            else if(onesrver.getServer_type().equals("modbus")){
                modusiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
            else if(onesrver.getServer_type().equals("rxtx")){
                rxtxiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
        }
        // 在plc列表中，下发fins握手指令
        if(finsiplist.contains(serverpath)){
            log.info("客户端与fins-plc：" + host + ":" + port + "连接成功!");
            // 握手连接  udp 6e = 110（传入本机ip，最后的数）
            FinsMessage finsMessage = new FinsMessage(Constants.DEFAULT_SA1);
            // 通道建立后，立马下发握手指令
            ctx.writeAndFlush(finsMessage.toString());
//        ReferenceCountUtil.release(finsMessage);
//        定时心跳机制
//        this.channel = ctx.channel();
//        ping(ctx.channel());
        }
        else if(modusiplist.contains(serverpath)){
            log.info("客户端与modbus设备：" + host + ":" + port + "连接成功!");
        }
        else if(rxtxiplist.contains(serverpath)){
            log.info("客户端与串口服务器设备：" + host + ":" + port + "连接成功!");
        }
        NettyStarter.setConnect(host, port, ctx.channel().newSucceededFuture());
        super.channelActive(ctx);
    }

   /* private void ping(Channel channel) {
        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                if (channel.isActive()) {
                    channel.writeAndFlush(HEART_BEAT);
                } else {
                    channel.closeFuture();
                    throw new RuntimeException();
                }
            }
        }, 10L, TimeUnit.SECONDS);
        future.addListener(new GenericFutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (future.isSuccess()) {
                    ping(channel);
                }
            }
        });
    }*/
}
